---
sidebar_position: 4
description: Migration guide from v2 to v3
keywords: [bottom sheet migration, upgrading bottom sheet, v2 to v3, breaking changes]
---

# Migrating to v3

This guide will help you migrate from TrueSheet v2 to v3. Version 3 brings full Fabric (New Architecture) support and several improvements.

## Breaking Changes

### 1. Fabric Architecture Required

Version 3 is built exclusively for React Native's **New Architecture (Fabric)**. The old Paper architecture is no longer supported.

**Requirements:**
- React Native >= 0.76 (Expo SDK 52+)
- New Architecture enabled (default in RN 0.76+)

:::tip
If you're on React Native 0.76+, the New Architecture is enabled by default. No additional configuration is needed.
:::

### 2. Prop Renames

Several props have been renamed for better clarity:

| v2 Prop | v3 Prop |
| - | - |
| `sizes` | `detents` |
| `initialIndex` | `initialDetentIndex` |
| `initialIndexAnimated` | `initialDetentAnimated` |
| `contentContainerStyle` | _(removed - no longer needed)_ |
| `FooterComponent` | `footer` |
| `edgeToEdge` | `edgeToEdgeFullScreen` |
| `onPresent` | `onDidPresent` |
| `onDismiss` | `onDidDismiss` |
| `onSizeChange` | `onDetentChange` |
| `dimmedIndex` | `dimmedDetentIndex` |

**Migration:**

```tsx
// ❌ v2
<TrueSheet
  sizes={['auto', '50%', '100%']}
  initialIndex={1}
  initialIndexAnimated={false}
  contentContainerStyle={styles.container}
  FooterComponent={<MyFooter />}
  edgeToEdge
  onPresent={(e) => console.log(e.nativeEvent)}
  onDismiss={() => console.log('dismissed')}
  onSizeChange={(e) => console.log(e.nativeEvent)}
/>

// ✅ v3
<TrueSheet
  detents={['auto', 0.5, 1]}
  initialDetentIndex={1}
  initialDetentAnimated={false}
  footer={<MyFooter />}
  edgeToEdgeFullScreen
  onDidPresent={(e) => console.log(e.nativeEvent)}
  onDidDismiss={() => console.log('dismissed')}
  onDetentChange={(e) => console.log(e.nativeEvent)}
/>
```

### 3. Removed Props

The following props have been removed in v3:

#### `grabberProps` (Android)

The `grabberProps` prop has been removed. The grabber is now rendered **natively** on both iOS and Android following Material Design 3 specifications. Use the `grabber` boolean prop to show or hide the native grabber.

**Migration:**

```tsx
// ❌ v2
<TrueSheet grabberProps={{ color: 'red', width: 40 }}>
  {/* ... */}
</TrueSheet>

// ✅ v3 - grabber is native, use boolean to show/hide
<TrueSheet grabber={true}>
  {/* ... */}
</TrueSheet>
```

#### `scrollRef` (iOS)

The `scrollRef` prop has been removed. Scroll views are now **automatically detected** on iOS. Use the new `scrollable` prop to enable automatic ScrollView pinning.

**Migration:**

```tsx
// ❌ v2
const scrollViewRef = useRef<ScrollView>(null)

<TrueSheet scrollRef={scrollViewRef}>
  <ScrollView ref={scrollViewRef}>{/* ... */}</ScrollView>
</TrueSheet>

// ✅ v3
<TrueSheet scrollable>
  <ScrollView>{/* ... */}</ScrollView>
</TrueSheet>
```

:::tip
See the [Scrolling guide](/guides/scrolling) for more information about `scrollable`.
:::

#### `contentContainerStyle`

The `contentContainerStyle` prop has been removed as it's no longer needed with the new architecture. Style your content directly instead.

### 4. Detent Value Changes

Detent values now use **fractional values (0-1)** instead of percentage strings.

**Migration:**

```tsx
// ❌ v2
<TrueSheet detents={["50%", "80%", "100%"]} />

// ✅ v3
<TrueSheet detents={[0.5, 0.8, 1]} />
```

:::tip
The `"auto"` detent still works the same way!
:::

### 5. Native Bottom Safe Area Handling

Both iOS and Android now handle bottom safe area insets natively for the sheet. This means the sheet height includes the safe area automatically, providing consistent behavior across all devices.

**What this means:**
- The `position`, `index`, and `detent` values are calculated purely from the actual sheet position relative to screen height
- A sheet at detent `0.5` will be taller than `0.5 * screenHeight` because the system adds the bottom safe area internally

**Footer with auto detent:**

The footer is pinned to the bottom edge of the sheet. When using an `auto` detent, the sheet height includes the safe area, so your footer needs to extend into it:

```tsx
import { Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const isIPad = Platform.OS === 'ios' && Platform.isPad;

const MyFooter = () => {
  const insets = useSafeAreaInsets();
  const bottomInset = isIPad ? 0 : insets.bottom;

  return (
    <View style={{ paddingBottom: bottomInset, backgroundColor: '#333' }}>
      <View style={{ height: 60, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Footer Content</Text>
      </View>
    </View>
  );
};

// Usage
<TrueSheet detents={['auto']} footer={<MyFooter />}>
  {/* content */}
</TrueSheet>
```

This ensures the footer background extends into the safe area while keeping the content above the home indicator.

:::note
On iPad, the sheet is displayed as a floating modal, so bottom padding is not needed.
:::

:::tip
See the [Footer guide](/guides/footer) for more details.
:::

### 6. Background and Blur Behavior Changes

#### `backgroundColor` Default Value

The `backgroundColor` prop no longer defaults to `"white"`. It now uses the **system default** when not provided, which matches the platform's native sheet background.

**Migration:**

```tsx
// ❌ v2 - backgroundColor defaults to white
<TrueSheet />

// ✅ v3 - backgroundColor uses system default (transparent on iOS)
// If you want white background, explicitly set it:
<TrueSheet backgroundColor="white" />
```

#### `blurTint` No Longer Overrides `backgroundColor`

In v2, setting `blurTint` would completely override the `backgroundColor`. In v3, `blurTint` now **applies a blur effect over** the `backgroundColor`, allowing you to combine both for richer visual effects.

**Migration:**

```tsx
// ❌ v2 - blurTint overrides backgroundColor (backgroundColor is ignored)
<TrueSheet backgroundColor="red" blurTint="light" />

// ✅ v3 - blurTint applies over backgroundColor (red + light blur)
<TrueSheet backgroundColor="red" blurTint="light" />

// If you want only blur without background color:
<TrueSheet blurTint="light" />
```

## New Features in v3

### 1. Automatic Edge-to-Edge Detection (Android)

TrueSheet now automatically detects and adapts to Android's edge-to-edge display mode. By default, the sheet respects the status bar when fully expanded.

For a true full-screen experience where the sheet extends behind the status bar, use the new `edgeToEdgeFullScreen` prop:

```tsx
<TrueSheet edgeToEdgeFullScreen />
```

:::info
This feature is **Android-only** and requires React Native 0.81+ with `edgeToEdgeEnabled=true` in your `android/gradle.properties`.
:::

See the [Edge-to-Edge guide](/guides/edge-to-edge) for details.

### 2. Enhanced Event System

Version 3 introduces new lifecycle and interaction events for better control:

#### New Events:
- **`onMount`** - Called when the sheet's content is mounted and ready
- **`onWillPresent`** - Called when the sheet is about to be presented
- **`onDidPresent`** - Called when the sheet has been presented
- **`onWillDismiss`** - Called when the sheet is about to be dismissed
- **`onDidDismiss`** - Called when the sheet has been dismissed
- **`onDragBegin`** - Called when the sheet begins dragging
- **`onDragChange`** - Called continuously while the sheet is being dragged
- **`onDragEnd`** - Called when sheet dragging has ended
- **`onPositionChange`** - Called continuously when the sheet's position changes
- **`onWillFocus`** - Called when the sheet is about to regain focus after a sheet on top begins dismissing
- **`onDidFocus`** - Called when the sheet has regained focus after a sheet on top is dismissed
- **`onWillBlur`** - Called when the sheet is about to lose focus because another sheet is about to be presented on top
- **`onDidBlur`** - Called when the sheet has lost focus because another sheet is presented on top

These events provide more granular control over the sheet's lifecycle and user interactions.

See the [Lifecycle Events documentation](/reference/events) for detailed usage and examples.

### 3. iPad Support with Page Sizing

Version 3 now includes first-class iPad support with the new `pageSizing` prop. When enabled (default), the sheet uses a large page sheet for better readability on iPad. When disabled, the sheet uses a centered form sheet.

```tsx
<TrueSheet pageSizing />
```

:::info
This feature is **iOS 17+ only** and defaults to `true`. Set to `false` to use a centered form sheet presentation on iPad.
:::

### 4. Improved Performance

- Faster rendering with Fabric architecture
- Built-in lazy loading by default
- Smoother animations and transitions

### 5. First-Class Reanimated v4 Support

Version 3 introduces dedicated components for seamless integration with `react-native-reanimated` v4:

#### New Components:
- **`ReanimatedTrueSheetProvider`** - Context provider for managing shared values
- **`ReanimatedTrueSheet`** - Drop-in replacement for `TrueSheet` with automatic position tracking
- **`useReanimatedTrueSheet`** - Hook to access the sheet's animated position

#### Example:

```tsx
import {
  ReanimatedTrueSheetProvider,
  ReanimatedTrueSheet,
  useReanimatedTrueSheet,
} from '@lodev09/react-native-true-sheet/reanimated'
import Animated, { useAnimatedStyle, interpolate, Extrapolation } from 'react-native-reanimated'

// 1. Wrap your app with the provider
const App = () => (
  <ReanimatedTrueSheetProvider>
    <YourApp />
  </ReanimatedTrueSheetProvider>
)

// 2. Use ReanimatedTrueSheet instead of TrueSheet
const MySheet = () => (
  <ReanimatedTrueSheet detents={[0.3, 0.6, 1]} initialDetentIndex={1}>
    <Text>Sheet Content</Text>
  </ReanimatedTrueSheet>
)

// 3. Access the sheet's position for custom animations
const MyAnimatedComponent = () => {
  const { animatedPosition, animatedIndex } = useReanimatedTrueSheet()

  const positionStyle = useAnimatedStyle(() => ({
    transform: [{ translateY: -animatedPosition.value }],
  }))

  // Example: Fade in as sheet expands from index 0 to 1
  const opacityStyle = useAnimatedStyle(() => ({
    opacity: interpolate(animatedIndex.value, [0, 1], [0, 1], Extrapolation.CLAMP)
  }))

  return (
    <Animated.View style={[opacityStyle, positionStyle]}>
      <Text>This moves with the sheet</Text>
    </Animated.View>
  )
}
```

:::tip
The `onPositionChange` event now runs on the UI thread (worklet) when using `ReanimatedTrueSheet`. Make sure to add the `'worklet'` directive to your handler if you override it.
:::

See the [Reanimated Integration guide](/guides/reanimated) for complete examples.

### 6. Native Header Support

Version 3 introduces a new `header` prop for adding fixed headers above your scrollable content. The header is rendered as a native view and is properly accounted for in layout calculations.

```tsx
<TrueSheet header={<MyHeader />}>
  <FlatList data={items} renderItem={renderItem} />
</TrueSheet>
```

This ensures proper scrolling behavior on both iOS and Android when using `ScrollView` or `FlatList` with a header.

See the [Header guide](/guides/header) for more details.

### 7. Draggable Control

Version 3 introduces the `draggable` prop to disable user dragging entirely. When set to `false`, the sheet becomes static and can only be resized programmatically using the [`resize`](/reference/methods#resize) method.

```tsx
<TrueSheet draggable={false} detents={[0.5, 1]}>
  <Button title="Expand" onPress={() => sheet.current?.resize(1)} />
</TrueSheet>
```

:::info
When `draggable` is `false`, the grabber is automatically hidden.
:::

See the [Configuration reference](/reference/configuration#draggable) for more details.

### 8. Blur Intensity and Interaction Control

Version 3 introduces new props for fine-grained control over the blur effect on iOS:

- **`blurIntensity`** - Control the blur intensity (0-100)
- **`blurInteraction`** - Enable/disable user interaction on the blur view

```tsx
<TrueSheet
  blurTint="light"
  blurOptions={{
    intensity: 80,
    interaction: false,
  }}
/>
```

See the [Configuration reference](/reference/configuration#bluroptions) for more details.

### 9. React Native Screens Integration

Version 3 works seamlessly with React Navigation! You can navigate to other screens from within a sheet and the sheet will remain visible in the background when presenting modals on top.

:::note
This requires changes to `react-native-screens`. There is a [pending PR](https://github.com/software-mansion/react-native-screens/pull/3415) that adds support for this. In the meantime, you can apply the patch from the [example app](https://github.com/lodev09/react-native-true-sheet/blob/main/.yarn/patches/react-native-screens-npm-4.18.0-fa7de65975.patch).
:::

```tsx
const sheet = useRef<TrueSheet>(null)

const navigate = () => {
  // Navigate directly - no need to dismiss first!
  navigation.navigate('SomeScreen')
}

return (
  <TrueSheet ref={sheet}>
    <Button onPress={navigate} title="Navigate to SomeScreen" />
  </TrueSheet>
)
```

See the [React Navigation guide](/guides/navigation) for more details.

## Step-by-Step Migration

1. **Enable New Architecture** in your React Native app (see above)
2. **Update the package:**
   ```sh
   yarn add @lodev09/react-native-true-sheet@^3.0.0
   ```
   ```sh
   npm install @lodev09/react-native-true-sheet@^3.0.0
   ```

3. **Clean and reinstall iOS dependencies:**
   ```sh
   cd ios
   rm -rf Pods Podfile.lock build
   pod install
   cd ..
   ```

4. **Clean Android build:**
   ```sh
   cd android
   ./gradlew clean
   cd ..
   ```

5. **Update your code:**
   - Replace `sizes` with `detents`
   - Replace `initialIndex` with `initialDetentIndex`
   - Replace `initialIndexAnimated` with `initialDetentAnimated`
   - Replace `FooterComponent` with `footer`
   - Replace `edgeToEdge` with `edgeToEdgeFullScreen`
   - Replace `onPresent` with `onDidPresent`
   - Replace `onDismiss` with `onDidDismiss`
   - Replace `onSizeChange` with `onDetentChange`
   - Replace `dimmedIndex` with `dimmedDetentIndex`
   - Remove `scrollRef` prop (iOS - now auto-detected)
   - Remove `contentContainerStyle` prop (no longer needed)
   - Remove `grabberProps` prop (grabber is now native on both platforms)
   - Change detent percentage strings to fractional numbers (e.g., `"50%"` → `0.5`)

6. **Test your app:**
   ```sh
   npx react-native run-ios
   npx react-native run-android
   ```

## Need Help?

If you encounter any issues during migration:

- Check the [Troubleshooting](/troubleshooting) guide
- Open an issue on [GitHub](https://github.com/lodev09/react-native-true-sheet/issues)
- Review the [example app](https://github.com/lodev09/react-native-true-sheet/tree/main/example) for reference implementations
